#! /usr/bin/env python
# $Id: dataonestat.py 23 2007-04-15 06:33:31Z prashmohan $
""" DataOne usage finding service

Retrieve the service records from the accounting site and parse the table to
calculate the bandwidth usage. Some plans have free usage period from 0200 -
0800 hrs, any data downloaded during this time in not accounted.

The results might be slightly inaccurate if a session crosses the boundry b/w
free and paid usage hours, because we assume that data downloaded during this
period is evenly distributed, which might not be true.

Usage: python dataonestat.py [-u username [-p passsword]] [-d date] [-s plan]

"""

__author__ = ['Prashanth Mohan <prashmohan@gmail.com>',
              'Vijairaj R <vijairaj.r@gmail.com>',
              'Baishampayan Ghose <b.ghose@ubuntu.com>']

__revision__ = "$Revision: 23 $"[11:-2]

import re
import urllib2
import cookielib

from os import path, environ, sep
from urllib import quote as urlquote
from sgmllib import SGMLParser
from time import localtime, strftime, gmtime

# try importing configobj for configuration file support
try:
    from configobj import ConfigObj
    CONFIG = True
    HOME = environ.get("HOME")
    CONFIG_FILE = '.dataonestat'
    CONFIG_FILE_PATH = HOME + sep + CONFIG_FILE
except ImportError:
    CONFIG = False

# -------------------------------------------------------------------- #

# Change to your own username or provide as Command Line input
CFG_USER_NAME = 'username'
# Change to your own password or provide as Command Line input
CFG_PASSWD = 'password'

# -------------------------------------------------------------------- #

class RecordParser(SGMLParser):
    """ Parse the HTML File and get the required info

    Read the information from the html file.
    We are interested only in TABLE 5 which contains the records.
    The First 2 TRs are headings and not needed.

    There is a bug in the HTML file - table4 doesnot end properly.

    """
    def reinit(self):
        self.table_c = 0
        self.tr_c    = 0
        self.td      = False
        self.new_links = []
        self.row = []

    def reset(self):
        SGMLParser.reset(self)
        self.is_valid_record = lambda: (self.table_c == 5 and self.tr_c > 2)
        self.reinit()
        self.table = []
        environ["TZ"] = "GMT" # Avoid complexity

    def feed(self, data):
        self.reinit()
        SGMLParser.feed(self, data)
        SGMLParser.close(self)  # Flush the data stream

    def start_a(self, attr):
        self.new_links.append(attr[0][1])

    def start_table(self, attr):
        self.table_c += 1

    def start_tr(self, attr):
        if self.table_c == 5:
            self.tr_c += 1

    def end_tr(self):
        if self.is_valid_record():
            self.table.append(self.row)
            self.row = []

    def start_td(self, attr):
        self.td = True

    def end_td(self):
        self.td = False

    def end_table(self):
        if self.table_c == 5:
            self.table_c -= 1

    def handle_data(self, data):
        if (self.is_valid_record() and self.td):
            self.row.append(data)


class DataOneUsage:
    """ This is the class which holds each connection details """
    ACCOUNT = { # plan-name : (plan-option, free-bw, charge-per-extra-mb)
        "HOME UL 900 Plus": ("1085|Plan-%20HOME%20UL%20900%20Plus",
                             0,0),
        "HOME UL 900": ("1086|Plan- HOME UL 900",
                        0, 0),
        "HOME 500": ("1069|HOME 500 downloadspeed -Upto 2 Mbps",
                     2.5*1024*1024, 0.8),
# 	"Speed 256 Kbps": ("1038|Speed 256 Kbps",
#                        1024*1024, 1.2),
        "HOME 250": ("1056|Startup Plan,downloadspeed  Upto 2 Mbps",
                     1024*1024, 0.9),
        "HOME 1000": ("1032|HOME 1000,downloadspeed Upto 2 Mbps",
                      5*1024*1024, 0.8),
        "HOME 1800": ("1076|HOME 1800:downloadspeed Upto 2 Mbps",
                      10*1024*1024, 0.7),
        "HOME 3300": ("1070|HOME 3300, downloadspeed upto 2 Mbps",
                      20*1024*1024, 0.7),
        "BUSINESS 9000": ("1048|Plan-BUSINESS 9000,download - 2048 Kbps",
                          75*1024*1024, 0.5),
        "BUSINESS 3000": ("1034|BUSINESS 3000,downloadspeed Upto 2 Mbps",
                          20*1024*1024, 0.5),
        "BUSINESS 5000": ("1035|BUSINESS 5000,downloadspeed Upto 2 Mbps",
                          35*1024*1024, 0.5),
        "BUSINESS 700": ("1057|BUSINESS 700,downloadspeed Upto 2 Mbps",
                         4*1024*1024, 0.8),
        "BUSINESS 1200": ("1073|BUSINESS 1200:downloadspeed upto 2Mbps",
                          8*1024*1024, 0.8)
	}
    CFG_DBG = 0
    (id_sname, id_startt, id_endt, id_sendkb, id_rcvkb, id_totkb,
     id_tots, id_bsnl_paidkb) = range(0, 8)

    def __init__ (self, username, passwd, date=None, plan=None):
        """ Initialise Class variables """
        if not date:
            date = strftime ("%Y-%m", localtime())
        self.plan = plan
        self.cfg_login_data = ("username=" + username + "&"
                               + "password=" + passwd )
        self.cfg_service_req_data = ("username=" + username + "&"
                                     + "queryDate=" + date + "&"
                                     + "idAndName=" # + self.ACCOUNT[plan][0]
                                     + "&ispId=")
        self.cfg_url = "http://10.240.144.195/"
        self.cfg_url_login = self.cfg_url + "nonie_webLogin.jsp"
        self.cfg_secu_login = self.cfg_url + 'secu/myportallogin.jsp'
        self.cfg_url_record = (self.cfg_url
                               + "/myportal/serviceRecords.jsp?"
                               + urlquote(strftime("%a, %d %b %Y %H:%M:%S"
                                                            + " +0000",
                                                   gmtime()), ',:+'))
        self.cfg_url_service_results = (self.cfg_url
                                        + "/myportal/serviceResults.jsp")
        self.cfg_service_next_page = "?pageIndex=%d&"

    def __format_string (self, amt):
        """ Changes Bytes into Human readable format"""
        if amt < 1024:
            amt = str(amt)+' KB'
        elif amt/1024 < 1024:
            amt = str(round(amt/1024.0, 2)) + ' MB'
        else:
            amt = str(round(amt/1024.0**2, 2)) + ' GB'
        return amt

    def __format_time (self, total_time):
        """ Changes seconds into Hours:Minutes:Seconds format"""
        hours = total_time/3600
        total_time -= hours*3600
        minutes = total_time/60
        total_time -= minutes*60
        return str(hours) + ':' + str(minutes) + ':' + str(total_time)

    def __get_records(self):
        """ Read the service records and feed it to the parser, finally return
        the service record table as a list.

        This method is highly specific and depends on the site-map of the
        accounting site as on date. If theere is any change in that site,
        suitable modifications should be incorprated here.

        """
        http_handler = urllib2.HTTPHandler(debuglevel=self.CFG_DBG)

        cj = cookielib.CookieJar()
        cookie_opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
        cookie_handler = urllib2.HTTPCookieProcessor(cj)
        # Auto cookie handling

        handlers = (http_handler, cookie_handler)
        urllib2.install_opener(cookie_opener)

        # Get the Login page
        req = urllib2.Request(self.cfg_url, None)
        f = urllib2.urlopen(req)
        f.read()
        f.close()

        # Post User name and password
        req = urllib2.Request(self.cfg_url_login, None)
        f = urllib2.urlopen(req)
        f.read()
        f.close()

        req = urllib2.Request(self.cfg_secu_login, self.cfg_login_data)
        f = urllib2.urlopen(req)
        data = f.read()
        f.close()

        # Javascript redirection, get the new URL
        next_url = re.findall("http:\/\/[^\"\']*", data)[0]

        # Go to the post login page
        req = urllib2.Request(next_url, None)
        f = urllib2.urlopen(req)
        data = f.read()
        f.close()

        # redirect again!!!!
        next_url = self.cfg_url + 'myportal/' + data[data.find('\'') \
                                                     + 1:data.rfind('\'')]
        # Go to the post login page
        f = urllib2.urlopen(next_url)
        f.read()
        f.close()

        # Go to the service record page
        f = urllib2.urlopen(self.cfg_url_record)
        f.read()
        f.close()

        # Read the first page
        f = urllib2.urlopen(self.cfg_url_service_results,
                            self.cfg_service_req_data)
        data = f.read()
        if not self.plan: # Plan name has not been specified
            for key, val in self.ACCOUNT.items():
                val = val[0][5:] # First 5 Characters is some wierd code for
                    # the plan
                if data.find(val) != -1:
                    self.plan = key
                    break
        f.close()

        pageIndex = 1
        parser = RecordParser()
        f = urllib2.urlopen(self.cfg_url_service_results,
                            self.cfg_service_req_data)
        data = f.read()
        f.close()

        # Read the rest of the pages and feed it to the parser
        while True:
            parser.feed(data)
            pageIndex += 1
            nextpage = self.cfg_service_next_page % pageIndex

            if not path.basename(self.cfg_url_service_results) + nextpage \
                   in parser.new_links:
                break

            f = urllib2.urlopen(self.cfg_url_service_results + nextpage)
            data = f.read()
            f.close()
        return parser.table

    def get_plans(self):
        """ Return all the plans supported by the script"""
        return self.ACCOUNT.keys()

    def get_usage(self):
        """ Calculate the Usage Data based on plan details.
        Returns:
        1. Table with start-time, end-time, total-bw, paid-bw
        2. Total Sent Traffic
        3. Total Received Traffic
        4. Total Traffic (Sum of 2 and 3)
        5. Total time of browsing
        6. Total Paid Traffic
        7. Total Free Traffic
        8. Exceeding Traffic
        9. Amount Payable (Extra)
        """
        table = self.__get_records()
        total_time = 0
        total_sent = 0
        total_rcvd = 0
        total_traf = 0
        paid_traf  = 0
        usage = []
        for row in table:
            total_sent += int(row[self.id_sendkb])
            total_rcvd += int(row[self.id_rcvkb])
            total_traf += int(row[self.id_totkb])
            total_time += int(row[self.id_tots])
            paid_traf  += int(row[self.id_bsnl_paidkb])
            usage.append ((row[self.id_startt], row[self.id_endt],
                           row[self.id_totkb], row[self.id_bsnl_paidkb]))
        print 'Calculating as per plan:', self.plan
        download_limit = self.ACCOUNT[self.plan][1]
        charge_per_mb = self.ACCOUNT[self.plan][2]
        if paid_traf <= download_limit:
            excess_traf = 0
        else:
            excess_traf = paid_traf - download_limit

        format_string = self.__format_string
        format_time = self.__format_time
        return (usage,
                format_string(total_sent),
                format_string(total_rcvd),
                format_string(total_traf),
                format_time(total_time),
                format_string(paid_traf),
                format_string(total_traf-paid_traf),
                format_string(excess_traf),
                str(round(excess_traf/1024.0*charge_per_mb,2)))


def get_args():
    """ Get Arguments from command line"""
    from optparse import OptionParser
    import sys
    usage = ("python dataonestat.py [-u username [-p passsword]]"
             + " [-d date] [-s plan]")
    parser = OptionParser(usage)
    parser.add_option ("-u", "--user", dest="username",
                       help="Specify your username")
    parser.add_option ("-p", "--password", dest="password", default = None,
                       help="Specify your password optionally. If not provided,"
                       + "password will be prompted")
    parser.add_option ("-d", "--date", dest="date",
                       help="Specify the Month of usage in yyyy-mm format")
    parser.add_option ("-s", "--service", dest="service",
                       help="Specify the Plan being used")
    parser.add_option ("-a", "--all", dest="verbose",
                       help="Prints out line by line usage in case if enabled")

    (options, args) = parser.parse_args()

    global CFG_USER_NAME, CFG_PASSWD
    CFG_DATE = None
    if options.date:
        CFG_DATE = options.date

    CFG_PLAN = None
    if options.service:
        CFG_PLAN = options.service
        
    VERBOSE = False
    if options.verbose:
        VERBOSE = True
        
    if CONFIG:
        try:
            config = ConfigObj(CONFIG_FILE_PATH) # load options
            CFG_USER_NAME = config['USER']
            CFG_PASSWD = config['PASS']
        except KeyError:
            pass
        
    if (options.username and options.password):
        # we accept username and password from the command line only
        # if both are defined
        CFG_USER_NAME = options.username
        CFG_PASSWD = options.password
    
    if CFG_USER_NAME == 'username' and CFG_PASSWD == 'password':
        sys.argv.append('-h')
        (options, args) = parser.parse_args()
    
    return CFG_USER_NAME, CFG_PASSWD, CFG_DATE, CFG_PLAN, VERBOSE


def main():
    """ Main Function executed only when called from Command line"""
    user_name, passwd, date, plan, verbosity = get_args()
    usage = DataOneUsage (user_name, passwd, date, plan)

    (table, total_sent, total_rcvd, total_traf, total_time, paid_traf,
     free_traf, excess_traf, amount) = usage.get_usage()
    if verbosity:
        print "\nStart time\t\t", "End time\t\t", "Total (KB)\t", "Billable (KB)"
        for row in table:
            print (row[0] + '\t' + row[1] + '\t' + row[2] + '\t\t' + row[3])

    print "\nTotal Sent:\t\t\t" + total_sent
    print "Total Received:\t\t\t" + total_rcvd
    print "Total Traffic:\t\t\t" + total_traf
    print "Total Time:\t\t\t" + total_time
    print "Billable Traffic:\t\t" + paid_traf
    print "Free Traffic:\t\t\t" + free_traf
    print "Excess Traffic:\t\t\t" + excess_traf
    print "Amount Chargeable:\t\t" + 'Rs. ' + amount + ' /-'

if __name__ == "__main__":
    main()
